using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Collections;

namespace AxeFrog
{
	public class Encryption
	{
		private int KeySize;

		//private int PublickeyLength;

		//private int PrivatekeyLength;

		public Encryption()
		{
			//Intitalising Members

			KeySize = 384;

			//public key length
			//PublickeyLength = 92;

			//private key lentgh
			//PrivatekeyLength = 316;

		}


		public void GenerateRandomKeyPair(out string publicKey, out string privateKey)
		{

			CspParameters CSPParam = new CspParameters();
			CSPParam.Flags = CspProviderFlags.UseMachineKeyStore;

			//establish RSA asymmetric algorithm
			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(KeySize, CSPParam);

			//Assigning public key string to public key param, exporting in string format
			publicKey = Convert.ToBase64String(rsa.ExportCspBlob(false));

			//Assigning private key string to private key param, exporting in string format
			privateKey = Convert.ToBase64String(rsa.ExportCspBlob(true));
		}

		/// <summary>
		/// Use the public key to encrypt the supplied input text.
		/// </summary>
		/// <param name="plainText">The text to encrypt</param>
		/// <param name="publicKey">The public key to use for the encryption</param>
		/// <returns>The encrypted output text</returns>
		/// 

		public string EncryptText(string plainText, string publicKey)
		{
			if(plainText == null)
				plainText = "";

			//RSA Key Size
			int dwKeySize = KeySize;

			//establish RSA asymmetric algorithm
			CspParameters csp = new CspParameters();
			csp.Flags = CspProviderFlags.UseMachineKeyStore;
			RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(dwKeySize, csp);

			//importing public key
			rsaCryptoServiceProvider.ImportCspBlob(Convert.FromBase64String(publicKey));

			//maximum Block size data chunk
			int keySize = dwKeySize / 8;

			byte[] bytes = Encoding.UTF32.GetBytes(plainText);

			//calulating max length of chunk
			int maxLength = keySize - 42;

			//length of Input Data
			int dataLength = bytes.Length;

			//calculating total number of iteration required to split data in chunks
			int iterations = dataLength / maxLength;

			StringBuilder stringBuilder = new StringBuilder();

			//with 384 byte key function can decrypt maximum 48 bytes; Splitting into max byte chunks            
			for (int i = 0; i <= iterations; i++)
			{
				byte[] tempBytes = new byte[(dataLength - maxLength * i > maxLength) ? maxLength : dataLength - maxLength * i];

				Buffer.BlockCopy(bytes, maxLength * i, tempBytes, 0, tempBytes.Length);

				byte[] encryptedBytes = rsaCryptoServiceProvider.Encrypt(tempBytes, true);

				Array.Reverse(encryptedBytes);

				stringBuilder.Append(Convert.ToBase64String(encryptedBytes));
			}
			//returning encrypted data string 

			return stringBuilder.ToString();

		}

		/// <summary>
		/// Decrypt the supplied text using a private key.
		/// </summary>
		/// <param name="encryptedText">Some text encrypted using the public key</param>
		/// <param name="privateKey">Some </param>
		/// <returns></returns>
		public string DecryptText(string encryptedText, string privateKey)
		{
			//key Size
			int dwKeySize = KeySize;

			//creating object of RSA service Provider
			CspParameters csp = new CspParameters();
			csp.Flags = CspProviderFlags.UseMachineKeyStore;
			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(dwKeySize, csp);

			//importing private key
			rsa.ImportCspBlob(Convert.FromBase64String(privateKey));

			//maximum Block size data chunk
			int base64BlockSize = ((dwKeySize / 8) % 3 != 0) ? (((dwKeySize / 8) / 3) * 4) + 4 : ((dwKeySize / 8) / 3) * 4;

			//calculating total number of iteration 
			int iterations = encryptedText.Length / base64BlockSize;

			//array list helper object
			ArrayList arrayList = new ArrayList();

			//with 384 byte key function can decrypt maximum 48 bytes; Splitting into max byte chunks
			for (int i = 0; i < iterations; i++)
			{

				byte[] encryptedBytes = Convert.FromBase64String(encryptedText.Substring(base64BlockSize * i, base64BlockSize));

				//RSACryptoServiceProvider reverses the order of encrypted bytes after encryption and before decryption.
				Array.Reverse(encryptedBytes);

				//adding decrypted chunk into array list helper object
				arrayList.AddRange(rsa.Decrypt(encryptedBytes, true));
			}

			//returning decrypted data string 
			return Encoding.UTF32.GetString(arrayList.ToArray(Type.GetType("System.Byte")) as byte[]);

		}

		/// <summary>
		/// Using the supplied private key, this will create a digital signature from the input text.
		/// </summary>
		/// <param name="inputText">This will be some text I pass in and could be of any arbitrary length</param>
		/// <param name="privateKey">The private key used to generate the digital signature</param>
		/// <returns>A string representation of the digital signature</returns>

		public string CreateDigitalSignature(string inputText, string privateKey)
		{
			if(string.IsNullOrEmpty(privateKey))
				throw new Exception("Missing private key");

			CspParameters CSPParam = new CspParameters();
			CSPParam.Flags = CspProviderFlags.UseMachineKeyStore;

			//get original message as byte array
			byte[] messagebytes = Encoding.UTF32.GetBytes(inputText);

			//create digest of original message using SHA1
			SHA1 sha1 = new SHA1CryptoServiceProvider();

			byte[] hashbytes = sha1.ComputeHash(messagebytes);

			//establish RSA asymmetric algorithm
			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(CSPParam);

			//Importing private key in to RSA 
			rsa.ImportCspBlob(Convert.FromBase64String(privateKey));

			//sign hash using OID for SHA-1
			return Convert.ToBase64String(rsa.SignHash(hashbytes, "1.3.14.3.2.26"));

		}

		/// <summary>
		/// This method should use the public key to verify that the supplied signature is correct for the supplied input text.
		/// The public key should not be able to generate the same digital signature of course.
		/// </summary>
		/// <param name="inputText">The input text that was used to generate the digital signature</param>
		/// <param name="signature">The digital signature that accompanies the input text</param>
		/// <param name="publicKey">The public key should be used to verify the signature</param>
		/// <returns>True if the signature is valid, otherwise False</returns>
		/// 

		public bool VerifyDigitalSignature(string inputText, string signature, string publicKey)
		{

			bool match;

			try
			{

				//get possibly modified message as byte array
				byte[] messagebytes = Encoding.UTF32.GetBytes(inputText);

				//create digest of original message using SHA1
				SHA1 sha1 = new SHA1CryptoServiceProvider();

				byte[] hashbytes = sha1.ComputeHash(messagebytes);

				//create RSA object using parameters from signing
				RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

				//Importing RSACryptoServiceProviderpublic key
				rsa.ImportCspBlob(Convert.FromBase64String(publicKey));

				//do verification on hash using OID for SHA-1
				match = rsa.VerifyHash(hashbytes, "1.3.14.3.2.26", Convert.FromBase64String(signature));

			}
			catch
			{
				match = false;
			}

			//retuen result
			return match;

		}

		/// <summary>
		/// This is a simple 2-way encryption method. If passed encrypted text, it will decrypt it. If passed plain text,
		/// it will encrypt it. The supplied encryption key is used to control the encryption.
		/// </summary>
		/// <param name="encryptionKey">The encryption key</param>
		/// <param name="text">Encrypted or decrypted text</param>
		/// <returns>The supplied text, encrypted or decrypted; i.e. the opposite state in which it was supplied</returns>
		public string EncryptDecrypt(string encryptionKey, string text)
		{

			// We are now going to create an instance of the Rihndael class.  

			RijndaelManaged RijndaelCipher = new RijndaelManaged();

			// First we need to turn the input strings into a byte array.
			byte[] PlainText = System.Text.Encoding.Unicode.GetBytes(text);

			byte[] Salt = Encoding.ASCII.GetBytes(encryptionKey.Length.ToString());

			bool EncryptData = false;

			string ReturnData = string.Empty;

			// The (Secret Key) will be generated from the specified password and salt.
			PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(encryptionKey, Salt);

			// Create a encryptor from the existing SecretKey bytes.
			ICryptoTransform CryptoTransform = null;

			MemoryStream MS = null;

			CryptoStream CS = null;

			string EncryptedData = string.Empty;

			byte[] Key = SecretKey.GetBytes(32);
			byte[] IV = SecretKey.GetBytes(16);

			//Needs to Encrpyt
			try
			{
				byte[] EncryptedDataBytes = Convert.FromBase64String(text);

				CryptoTransform = RijndaelCipher.CreateDecryptor(Key, IV);

				MS = new MemoryStream(EncryptedDataBytes);

				// Create a CryptoStream. (always use Read mode for decryption).
				CS = new CryptoStream(MS, CryptoTransform, CryptoStreamMode.Read);

				byte[] Text = new byte[EncryptedDataBytes.Length];

				// Start decrypting.

				int DecryptedCount = CS.Read(Text, 0, Text.Length);

				MS.Close();

				CS.Close();

				// Convert decrypted data into a string. 
				ReturnData = Encoding.Unicode.GetString(Text, 0, DecryptedCount);

			}
			catch
			{
				EncryptData = true;
			}

			if (EncryptData == true && ReturnData == string.Empty)
			{
				CryptoTransform = RijndaelCipher.CreateEncryptor(Key, IV);

				// Create a MemoryStream that is going to hold the encrypted bytes 

				MS = new MemoryStream();

				CS = new CryptoStream(MS, CryptoTransform, CryptoStreamMode.Write);

				// Start the encryption process.
				CS.Write(PlainText, 0, PlainText.Length);

				// Finish encrypting.
				CS.FlushFinalBlock();

				// Convert our encrypted data from a memoryStream into a byte array.
				byte[] CipherBytes = MS.ToArray();

				// Convert encrypted data into a base64-encoded string.
				ReturnData = Convert.ToBase64String(CipherBytes);

				// Close both streams.
				MS.Close();

				CS.Close();
				// Return encrypted string.

			}

			return ReturnData;

		}
	}
}